﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using TeBigScreenWpf.WordCloud;
using TeBigScreenWpf.WordCloud.Geometry;
using TeBigScreenWpf.WordCloud.TextAnalyses;
using TeBigScreenWpf.WordCloud.TextAnalyses.Blacklist;
using TeBigScreenWpf.WordCloud.TextAnalyses.Blacklist.En;
using TeBigScreenWpf.WordCloud.TextAnalyses.Extractors;
using TeBigScreenWpf.WordCloud.TextAnalyses.Processing;
using TeBigScreenWpf.WordCloud.TextAnalyses.Stemmers;
using TeBigScreenWpf.WordCloud.TextAnalyses.Stemmers.En;

namespace TeBigCommonUI.Items
{
    public class VisualHost : UIElement
    {
        public DrawingVisual Visual { get; set; } = new DrawingVisual();

        protected override int VisualChildrenCount
        {
            get { return Visual != null ? 1 : 0; }
        }

        protected override Visual GetVisualChild(int index)
        {
            return Visual;
        }
    }

    public class BaseCloudControl : BaseItemControl
    {
        public IEnumerable<IWord> DataSource
        {
            get { return (IEnumerable<IWord>)GetValue(DataSourceProperty); }
            set { SetValue(DataSourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for DataSource.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataSourceProperty =
            DependencyProperty.Register("DataSource", typeof(IEnumerable<IWord>), typeof(BaseCloudControl),
                new FrameworkPropertyMetadata(null,
                    FrameworkPropertyMetadataOptions.AffectsRender,
                    new PropertyChangedCallback(OnDataSourceChanged)));

        private static void OnDataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is CloudControl cloud)
            {
                void Incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs arg)
                {
                    if (sender is IEnumerable<IWord> v)
                    {
                        cloud.SetWords(v);
                    }
                }


                if (e.NewValue is IEnumerable<IWord> value)
                {
                    cloud.SetWords(value);
                }

                if (e.OldValue is INotifyCollectionChanged old_incc)
                {
                    old_incc.CollectionChanged -= Incc_CollectionChanged;
                }

                if (e.NewValue is INotifyCollectionChanged new_incc)
                {
                    new_incc.CollectionChanged += Incc_CollectionChanged;
                }
            }
        }
    }

    public class CloudControl : BaseCloudControl
    {
        private IEnumerable<IWord> m_Words;

        private static readonly Color[] m_DefaultPalette = new[] {
            //Color.FromArgb(0xff, 41, 127, 213),
            //Color.FromArgb(0xff, 16, 207, 155),
            //Color.FromArgb(0xff, 165, 194, 73),
            //Color.FromArgb(0xff, 28, 173, 228),
            //Color.FromArgb(0xff, 74, 102, 172),
            //Color.FromArgb(0xff, 98, 157, 209),
            //Color.FromArgb(0xff, 127, 143, 169),
            //Color.FromArgb(0xff, 90, 162, 174),
            //Color.FromArgb(0xff, 157, 144, 160)
            Color.FromArgb(0xff, 0xd1, 0xc9, 0xe2),
            Color.FromArgb(0xff, 0xef, 0x84, 0xb8),
            Color.FromArgb(0xff, 0x5a, 0x9b, 0xa6),
            Color.FromArgb(0xff, 0x89, 0xd2, 0xcc),
            Color.FromArgb(0xff, 0xcb, 0xa0, 0x38),
            Color.FromArgb(0xff, 0xdb, 0x51, 0x4f),
            Color.FromArgb(0xff, 0xec, 0x76, 0x30),
            Color.FromArgb(0xff, 0x5a, 0xac, 0xaa),
        };

        private Color[] m_Palette = m_DefaultPalette;
        private LayoutType m_LayoutType = LayoutType.Spiral;

        private int m_MaxFontSize = 60;
        private int m_MinFontSize = 18;
        private ILayout m_Layout;
        private Color m_BackColor = Colors.White;
        private LayoutItem m_ItemUderMouse;
        private int m_MinWordWeight = 0;
        private int m_MaxWordWeight = 999;

        private VisualHost m_visual = new VisualHost();

        public CloudControl()
        {
            this.AddChild(m_visual);

            this.Loaded += CloudControl_Loaded;

            this.SizeChanged += CloudControl_SizeChanged;
        }

        public void SetWords(IEnumerable<IWord> words)
        {
            this.WeightedWords = words.SortByOccurences().Cast<IWord>();
        }

        private void CloudControl_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            BuildLayout();

            InvalidateVisual();
        }

        private void CloudControl_Loaded(object sender, RoutedEventArgs e)
        {
            //string[] words = new[]
            //{
            //    "小偷", "手机","出租车", "赔偿","责任","过失",
            //    "丢失","监控","商标","合同","挪用","自首","从犯","误工费","伪造",
            //    "恶意透支","寻衅滋事","赔偿责任","非法经营","未成年人","冒用", "票据","假冒",
            //    "赔偿", "交通事故","挪用公款","精神损害","和解","传销","合同诈骗",
            //    "超载","贷款","投资","精神损失费","保管","租赁",
            //    "贪污", "贿赂","侵犯财产","渎职","盗窃","走私", "火车站", "枢纽站", "公交车",
            //};

            //SetWords(words.Select(r=>new Word { Text = r, Occurrences = MyRandom.NextInt(1, 100) }));

            //ProcessText();
        }

        public static IEnumerable<IWord> ProcessText(string txt)
        {
            IBlacklist blacklist = ComponentFactory.CreateBlacklist(false);

            IEnumerable<string> terms = ComponentFactory.CreateExtractor(InputType.String, txt, new ProgressIndicator());
            IWordStemmer stemmer = ComponentFactory.CreateWordStemmer(false);

            IEnumerable<IWord> words = terms
                .Filter(blacklist)
                .CountOccurences();

            return words.GroupByStem(stemmer)
                    .SortByOccurences()
                    .Cast<IWord>();
        }

        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);

            if (m_Words == null) { return; }
            if (m_Layout == null) { return; }

            IEnumerable<LayoutItem> wordsToRedraw = m_Layout.GetWordsInArea(new Rectangle(new Point(0, 0), RenderSize));
            using (IGraphicEngine graphicEngine =
                    new WpfGraphicEngine(dc,
                    new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch)
                    , m_Palette, m_MinFontSize, m_MaxFontSize, m_MinWordWeight, m_MaxWordWeight))
            {
                foreach (LayoutItem currentItem in wordsToRedraw)
                {
                    if (m_ItemUderMouse == currentItem)
                    {
                        graphicEngine.DrawEmphasized(currentItem);
                    }
                    else
                    {
                        graphicEngine.Draw(currentItem);
                    }
                }
            }
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            ShowTooltip(null);

            base.OnMouseLeave(e);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            Point mousePositionRelativeToControl = e.GetPosition(this);

            LayoutItem nextItemUnderMouse;

            this.TryGetItemAtLocation(mousePositionRelativeToControl, out nextItemUnderMouse);

            if (nextItemUnderMouse != m_ItemUderMouse)
            {
                //if (nextItemUnderMouse != null)
                //{
                //    Rectangle newRectangleToInvalidate = RectangleGrow(nextItemUnderMouse.Rectangle, 6);
                //    this.Invalidate(newRectangleToInvalidate);
                //}
                //if (m_ItemUderMouse != null)
                //{
                //    Rectangle prevRectangleToInvalidate = RectangleGrow(m_ItemUderMouse.Rectangle, 6);
                //    this.Invalidate(prevRectangleToInvalidate);
                //}
                m_ItemUderMouse = nextItemUnderMouse;

                if (m_ItemUderMouse != null)
                    ShowTooltip($"{m_ItemUderMouse.Word.Text}: {m_ItemUderMouse.Word.Occurrences}" );
                else
                    ShowTooltip(null);
            }

            base.OnMouseMove(e);
        }



        private void ShowTooltip(string text)
        {
            if (text is null)
            {
                if (ToolTip is ToolTip tt)
                    tt.IsOpen = false;

                ToolTip = null;

                return;
            }

            if (ToolTip == null || !(ToolTip is ToolTip))
            {
                var tooltip = new ToolTip { Content = text, FontSize = 24 };

                tooltip.IsOpen = true;

                this.ToolTip = tooltip;
            }
            else if (ToolTip is ToolTip tooltip)
            {
                tooltip = (ToolTip)ToolTip;
                tooltip.Content = text;
            }

            
        }

        public bool TryGetItemAtLocation(Point location, out LayoutItem foundItem)
        {
            foundItem = null;
            IEnumerable<LayoutItem> itemsInArea = GetItemsInArea(new Rectangle(location, new Size(0, 0)));
            foreach (LayoutItem item in itemsInArea)
            {
                foundItem = item;
                return true;
            }
            return false;
        }

        private void BuildLayout()
        {
            if (m_Words == null) { return; }

            using (DrawingContext dc = m_visual.Visual.RenderOpen())
            {
                IGraphicEngine graphicEngine =
                     new WpfGraphicEngine(dc,
                     new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch)
                     , m_Palette, m_MinFontSize, m_MaxFontSize, m_MinWordWeight, m_MaxWordWeight);

                m_Layout = LayoutFactory.CreateLayout(m_LayoutType, RenderSize);
                m_Layout.Arrange(m_Words, graphicEngine);
            }
        }

        public LayoutType LayoutType
        {
            get { return m_LayoutType; }
            set
            {
                if (value == m_LayoutType)
                {
                    return;
                }

                m_LayoutType = value;
                BuildLayout();
                InvalidateVisual();
            }
        }

        private static Rectangle RectangleGrow(Rectangle original, int growByPixels)
        {
            return new Rectangle(
                (int)(original.X - growByPixels),
                (int)(original.Y - growByPixels),
                (int)(original.Width + growByPixels + 1),
                (int)(original.Height + growByPixels + 1));
        }

        public int MaxFontSize
        {
            get { return m_MaxFontSize; }
            set
            {
                m_MaxFontSize = value;
                BuildLayout();
                InvalidateVisual();
            }
        }

        public int MinFontSize
        {
            get { return m_MinFontSize; }
            set
            {
                m_MinFontSize = value;
                BuildLayout();
                InvalidateVisual();
            }
        }

        public Color[] Palette
        {
            get { return m_Palette; }
            set
            {
                m_Palette = value;
                BuildLayout();
                InvalidateVisual();
            }
        }

        public IEnumerable<IWord> WeightedWords
        {
            get { return m_Words; }
            set
            {
                m_Words = value;
                if (value == null) { return; }
                if (!m_Words.Any()) { return; }

                IWord first = m_Words.First();
                if (first != null)
                {
                    m_MaxWordWeight = first.Occurrences;
                    m_MinWordWeight = m_Words.Last().Occurrences;
                }

                BuildLayout();
                InvalidateVisual();
            }
        }

        public IEnumerable<LayoutItem> GetItemsInArea(Rectangle area)
        {
            if (m_Layout == null)
            {
                return new LayoutItem[] { };
            }

            return m_Layout.GetWordsInArea(area);
        }
    }

    internal enum InputType
    {
        String,
        Uri,
        File
    }

    public class ProgressIndicator : IProgressIndicator
    {
        public int Maximum { get; set; }

        public void Increment(int value)
        {
        }
    }

    internal static class ComponentFactory
    {
        public static IWordStemmer CreateWordStemmer(bool groupSameStemWords)
        {
            return groupSameStemWords
                       ? (IWordStemmer)new PorterStemmer()
                       : new NullStemmer();
        }

        public static IBlacklist CreateBlacklist(bool excludeEnglishCommonWords)
        {
            return excludeEnglishCommonWords
                       ? (IBlacklist)new CommonWords()
                       : new NullBlacklist();
        }

        public static IEnumerable<string> CreateExtractor(InputType inputType, string input, IProgressIndicator progress)
        {
            switch (inputType)
            {
                case InputType.File:
                    FileInfo fileInfo = new FileInfo(input);
                    return new FileExtractor(fileInfo, progress);

                case InputType.Uri:
                    Uri uri = new Uri(input);
                    return new UriExtractor(uri, progress);

                default:
                    return new StringExtractor(input, progress);
            }
        }

        public static InputType DetectInputType(string input)
        {
            if (input.Length < 0x200)
            {
                if (input.StartsWith("http", true, CultureInfo.InvariantCulture))
                {
                    return InputType.Uri;
                }
                if (File.Exists(input))
                {
                    return InputType.File;
                }
            }
            return InputType.String;
        }
    }

    public class Word : IWord
    {
        public string Text { get; set; }

        public int Occurrences { get; set; }

        public int CompareTo(IWord other)
        {
            return this.Occurrences - other.Occurrences;
        }

        public string GetCaption()
        {
            return string.Format("{0} - occurrences", Occurrences);
        }
    }
}